﻿namespace AgentJohnson.DocumentThrownExceptions
{
  using System.Linq;
  using AgentJohnson.CodeAnnotations;
  using Resharper.Abstractions.CodeDom.Statements.ThrowStatements;
  using Resharper.Abstractions.CodeDom.Statements.TryStatements;
  using Resharper.Abstractions.CodeDom.TreeNodes;
  using Resharper.Abstractions.CodeDom.TypeMembers;
  using Resharper.Abstractions.Daemons;
  using Resharper.Abstractions.DocumentThrownExceptions;
  using Resharper.Abstractions.Extensions.DocumentationExtensions;

  /// <summary>Defines the <see cref="DocumentThrownExceptionDaemon"/> class.</summary>
  [DaemonStage(StagesBefore = new[] { typeof(CodeAnnotationDaemon) })]
  public class DocumentThrownExceptionDaemon : DocumentThrownExceptionDaemonBase
  {
    #region Methods

    /// <summary>Processes the specified tree node.</summary>
    /// <param name="treeNode">The tree node.</param>
    protected override void Process(TreeNode treeNode)
    {
      var throwStatement = treeNode.GetThrowStatement();
      if (throwStatement == null)
      {
        return;
      }

      if (this.IsExceptionDocumented(throwStatement))
      {
        return;
      }

      if (this.IsExceptionCaught(throwStatement))
      {
        return;
      }

      var highlight = this.CreateHighlight<DocumentThrownExceptionHighlight>();

      highlight.Range = throwStatement.GetRange();
      highlight.ThrowStatement = throwStatement;
    }

    /// <summary>Determines whether [is exception caught] [the specified throw statement].</summary>
    /// <param name="throwStatement">The throw statement.</param>
    /// <returns><c>true</c> if [is exception caught] [the specified throw statement]; otherwise, <c>false</c>.</returns>
    private bool IsExceptionCaught(ThrowStatement throwStatement)
    {
      var exceptionType = throwStatement.ExceptionTypeName;

      var tryStatement = throwStatement.GetContainingTryStatement();

      while (tryStatement != null)
      {
        if (!tryStatement.Catches.Any(c => c.Contains(throwStatement)))
        {
          if (tryStatement.Catches.Any(c => c.ExceptionTypeName == exceptionType))
          {
            return true;
          }
        }

        tryStatement = tryStatement.GetContainingTryStatement();
      }

      return false;
    }

    /// <summary>Determines whether [is exception documented] [the specified throw statement].</summary>
    /// <param name="throwStatement">The throw statement.</param>
    /// <returns><c>true</c> if [is exception documented] [the specified throw statement]; otherwise, <c>false</c>.</returns>
    private bool IsExceptionDocumented(ThrowStatement throwStatement)
    {
      var typeMember = throwStatement.GetContainingTypeMember();
      if (typeMember == null)
      {
        return false;
      }

      var exceptionTypeName = throwStatement.EscapedExceptionTypeName;

      return typeMember.DocumentedExceptions().Any(c => c.EscapedExceptionTypeName == exceptionTypeName);
    }

    #endregion
  }
}